<template>
  <div class="webgl-sdf-curve">
    <h1>SDF 曲线展示</h1>
    <p>使用 SDF（Signed Distance Function）来绘制不同的2D形状。</p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>SDF 曲线效果展示</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="demoCanvas" width="600" height="400"></canvas>
        <div class="control-panel">
          <el-form label-width="100px">
            <el-form-item label="选择图形">
              <el-select v-model="selectedShape" placeholder="请选择" @change="updateShape">
                <el-option
                  v-for="item in shapeOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value">
                </el-option>
              </el-select>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>着色器代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code class="glsl">
  // 顶点着色器
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
  }
        </code></pre>
        <pre><code class="glsl" v-html="fragmentShaderSource"></code></pre>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { WebGLUtils } from '@/utils/webgl'

const demoCanvas = ref<HTMLCanvasElement | null>(null)
const shapeOptions = [
  {
    label: '线段',
    value: 'line',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 线段SDF
    // p: 当前片元坐标
    // a: 线段起点
    // b: 线段终点
    float sdSegment( in vec2 p, in vec2 a, in vec2 b ) {
        vec2 pa = p-a, ba = b-a;
        float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
        return length( pa - ba*h );
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        vec2 a = vec2(-0.5, -0.25);
        vec2 b = vec2(0.5, 0.25);
        float d = sdSegment(uv, a, b);
        vec3 col = (d > 0.01) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '圆',
    value: 'circle',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 圆SDF
    // p: 当前片元坐标
    // r: 圆的半径
    float sdCircle(vec2 p, float r) {
        return length(p) - r;
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        float d = sdCircle(uv, 0.5);
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '矩形',
    value: 'rectangle',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 矩形SDF
    // p: 当前片元坐标
    // b: 矩形半长宽
    float sdBox( in vec2 p, in vec2 b ) {
        vec2 d = abs(p)-b;
        return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        vec2 b = vec2(0.5, 0.25);
        float d = sdBox(uv, b);
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '圆角矩形',
    value: 'roundedRectangle',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 圆角矩形SDF
    // p: 当前片元坐标
    // b: 矩形半长宽
    // r: 圆角半径
    float sdRoundedBox( in vec2 p, in vec2 b, in float r ) {
        vec2 q = abs(p)-b+r;
        return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r;
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        vec2 b = vec2(0.5, 0.25);
        float r = 0.1;
        float d = sdRoundedBox(uv, b, r);
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '菱形',
    value: 'diamond',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 菱形SDF
    // p: 当前片元坐标
    // b: 菱形半长宽
    float sdRhombus( in vec2 p, in vec2 b ) {
        p = abs(p);
        vec2 ab = vec2(b.x, -b.y);
        vec2 ap = p - vec2(0.0, b.y);
        float h = clamp(dot(ab, ap) / dot(ab, ab), 0.0, 1.0);
        float s = sign(ab.x * ap.y - ap.x * ab.y);
        return s * length(ap - h * ab);
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        vec2 b = vec2(0.5, 0.5);
        float d = sdRhombus(uv, b);
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '五边形',
    value: 'pentagon',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 五边形SDF
    float sdPentagon( in vec2 p, in float r ) {
        // k 包含了 pi/5 的 cos, sin, tan 值，用于几何计算
        const vec3 k = vec3(0.809016994,0.587785252,0.726542528);
        p.y = -p.y;
        p.x = abs(p.x);
        // 通过两次反射将坐标点 p 折叠到第一象限的扇区内
        p -= 2.0*min(dot(vec2(-k.x,k.y),p),0.0)*vec2(-k.x,k.y);
        p -= 2.0*min(dot(vec2( k.x,k.y),p),0.0)*vec2( k.x,k.y);
        p.x = abs(p.x);
        // 计算点到五边形顶边的距离
        p -= vec2(min(p.x,r*k.z),r);
        return length(p)*sign(p.y);
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        float d = sdPentagon(uv, 0.5);
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '星形',
    value: 'star',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 星形SDF (by Inigo Quilez)
    float sdPentagram(in vec2 p, in float r) {
        // 定义五角星的几何常数，基于黄金分割比
        const float k1x = 0.809016994; // cos(pi/5)
        const float k1y = 0.587785252; // sin(pi/5)
        const float k2x = 0.309016994; // sin(pi/10)
        const float k2y = 0.951056516; // cos(pi/10)
        const float k1z = 0.726542528; // tan(pi/5)
        const vec2  v1  = vec2( k1x,-k1y);
        const vec2  v2  = vec2(-k1x,-k1y);
        const vec2  v3  = vec2( k2x,-k2y);

        // 将坐标点 p 折叠到第一象限的单个扇区，简化计算
        p.x = abs(p.x);
        p -= 2.0*max(dot(v1,p),0.0)*v1;
        p -= 2.0*max(dot(v2,p),0.0)*v2;
        p.x = abs(p.x);

        // 计算点到五角星尖端一条边的距离
        p.y -= r;
        return length(p-v3*clamp(dot(p,v3),0.0,k1z*r)) * sign(p.y*v3.x-p.x*v3.y);
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        float d = sdPentagram(uv, 0.8);
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '马蹄形',
    value: 'horseshoe',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 马蹄形SDF
    float sdHorseshoe( in vec2 p, in vec2 c, in float r, in vec2 w )
    {
        p.x = abs(p.x);
        float l = length(p);
        p = mat2(-c.x, c.y, c.y, c.x)*p;
        p = vec2( (p.y>0.0)?p.x:l*sign(-c.x), (p.x>0.0)?p.y:l );
        p = vec2(p.x,abs(p.y-r))-w;
        return length(max(p,0.0)) + min(0.0,max(p.x,p.y));
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        float d = sdHorseshoe(uv, vec2(cos(1.0),sin(1.0)), 0.5, vec2(0.1,0.3));
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  },
  {
    label: '维西卡形',
    value: 'vesica',
    shader: `
    precision mediump float;
    uniform vec2 iResolution;

    // 维西卡形SDF
    float sdVesica(in vec2 p, in float r, in float d) {
        p = abs(p);
        float b = sqrt(r*r-d*d);
        return ((p.y-b)*d>p.x*b) ? length(p-vec2(0.0,b))
                                 : length(p-vec2(-d,0.0))-r;
    }

    void main() {
        vec2 uv = (2.0*gl_FragCoord.xy-iResolution.xy)/iResolution.y;
        float d = sdVesica(uv, 0.5, 0.2);
        vec3 col = (d > 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.65, 0.85, 1.0);
        gl_FragColor = vec4(col, 1.0);
    }
  `
  }
];

const selectedShape = ref(shapeOptions[0].value)
let gl: WebGLRenderingContext | null = null
let program: WebGLProgram | null = null
const fragmentShaderSource = computed(() => {
  const selected = shapeOptions.find(option => option.value === selectedShape.value);
  return selected ? selected.shader : '';
});

const vertexShaderSource = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
  }
`

function updateShape() {
  if (demoCanvas.value) {
    initWebGL(demoCanvas.value)
    render()
  }
}

function initWebGL(canvas: HTMLCanvasElement) {
  gl = canvas.getContext('webgl')
  if (!gl) {
    console.error('WebGL not supported')
    return
  }

  const webglUtils = new WebGLUtils(gl)
  const vertexShader = webglUtils.createShader(gl.VERTEX_SHADER, vertexShaderSource)
  const fragmentShader = webglUtils.createShader(gl.FRAGMENT_SHADER, fragmentShaderSource.value)
  if (!vertexShader || !fragmentShader) return

  // 创建着色器程序
  program = webglUtils.createProgram(vertexShader, fragmentShader)

  if (!program) {
    console.error('Failed to initialize shaders.')
    return
  }

  gl.useProgram(program);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  const n = initVertexBuffers(gl, program)
  if (n < 0) {
    console.error('Failed to set the positions of the vertices')
    return
  }
}

function initVertexBuffers(gl: WebGLRenderingContext, program: WebGLProgram): number {
  const vertices = new Float32Array([-1, 1, -1, -1, 1, 1, 1, -1])
  const n = 4

  const vertexBuffer = gl.createBuffer()
  if (!vertexBuffer) {
    console.error('Failed to create the buffer object')
    return -1
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

  const a_Position = gl.getAttribLocation(program, 'a_Position')
  if (a_Position < 0) {
    console.error('Failed to get the storage location of a_Position')
    return -1
  }

  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
  gl.enableVertexAttribArray(a_Position)

  return n
}

function render() {
  if (!gl || !program || !demoCanvas.value) return

  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  const iResolution = gl.getUniformLocation(program, 'iResolution')
  gl.uniform2f(iResolution, gl.canvas.width, gl.canvas.height)

  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}

onMounted(() => {
  if (demoCanvas.value) {
    initWebGL(demoCanvas.value)
    render()

    const resizeObserver = new ResizeObserver(() => {
      if(demoCanvas.value && gl) {
        demoCanvas.value.width = demoCanvas.value.clientWidth;
        demoCanvas.value.height = demoCanvas.value.clientHeight;
        render()
      }
    });
    resizeObserver.observe(demoCanvas.value);
  }
})
</script>

<style lang="scss" scoped>
.webgl-sdf-curve {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;

  h1 {
    color: #303133;
    margin-bottom: 20px;
  }

  p {
    margin-bottom: 30px;
  }

  .demo-section,
  .code-section {
    margin: 30px 0;

    .card-header {
      display: flex;
      align-items: center;

      h3 {
        margin: 0;
        color: #409EFF;
      }
    }
  }

  .demo-section {
    .demo-content {
      display: flex;
      gap: 20px;
      justify-content: center;

      canvas {
        border: 1px solid #dcdfe6;
      }

      .control-panel {
        flex: 1;
        padding: 20px;
      }
    }
  }

  .code-section {
    .code-content {
      background-color: #f8f9fa;
      padding: 15px;
      border-radius: 4px;
      max-height: 500px;
      overflow-y: auto;

      code {
        font-family: monospace;
        color: #409EFF;
      }

      pre {
        margin: 0;
        white-space: pre-wrap;
      }
    }
  }
}
</style>
